﻿using System;
using System.ComponentModel.Composition;
using System.Windows;
using System.Linq;
using System.Windows.Media;
using System.Windows.Shapes;
using Microsoft.SilverlightMediaFramework.Plugins.Metadata;
using Microsoft.SilverlightMediaFramework.Plugins.Primitives;
using System.Collections.Generic;

namespace Microsoft.SilverlightMediaFramework.Plugins.Webcam
{
    [ExportMediaPlugin(PluginName = PluginName,
        PluginDescription = PluginDescription,
        PluginVersion = PluginVersion,
        SupportedDeliveryMethods = SupportedDeliveryMethodsInternal)]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class WebcamMediaPlugin : IMediaPlugin
    {
        private const string PluginName = "WebcamMediaPlugin";
        private const string PluginDescription = "Provides Webcam capabilities for the Silverlight Media Framework.";
        private const string PluginVersion = "2.2011.0410.1";
        private const DeliveryMethods SupportedDeliveryMethodsInternal = DeliveryMethods.NotSpecified;

        System.Windows.Media.VideoBrush videoBrush;
        Rectangle rectVideo;

        bool autoPlay;
        public bool AutoPlay
        {
            get { return autoPlay; }
            set
            {
                autoPlay = value;
                if (autoPlay) Play();
            }
        }

        public CacheMode CacheMode
        {
            get
            {
                return rectVideo.CacheMode;
            }
            set
            {
                rectVideo.CacheMode = value;
            }
        }

        public double Balance { get; set; }

        public double BufferingProgress
        {
            get { return 100; }
        }

        public TimeSpan BufferingTime { get; set; }

        public bool CanPause
        {
            get { return true; }
        }

        public bool CanSeek
        {
            get { return false; }
        }

        MediaPluginState currentState = MediaPluginState.Closed;
        public MediaPluginState CurrentState
        {
            get
            {
                return currentState;
            }
            set
            {
                currentState = value;
                if (CurrentStateChanged != null) CurrentStateChanged(this, currentState);
            }
        }

        void ResetState()
        {
            CurrentState = GetStateFromSource();
        }

        MediaPluginState GetStateFromSource()
        {
            if (captureSource == null) { return MediaPluginState.Closed; }
            switch (captureSource.State)
            {
                case CaptureState.Started:
                    return MediaPluginState.Playing;
                case CaptureState.Stopped:
                    return MediaPluginState.Stopped;
                default:
                    return MediaPluginState.Closed;
            }
        }

        public double DownloadProgress
        {
            get { return 100; }
        }

        public TimeSpan Duration
        {
            get { return TimeSpan.Zero; }
        }

        public TimeSpan EndPosition
        {
            get { return TimeSpan.Zero; }
        }

        public bool IsMuted { get; set; }

        public LicenseAcquirer LicenseAcquirer { get; set; }

        public double PlaybackRate { get; set; }

        public TimeSpan Position { get; set; }

        public TimeSpan StartPosition
        {
            get { return TimeSpan.Zero; }
        }

        public DeliveryMethods SupportedDeliveryMethods
        {
            get { return DeliveryMethods.NotSpecified; }
        }

        public System.Collections.Generic.IEnumerable<double> SupportedPlaybackRates
        {
            get { return new double[] { 1.0 }; }
        }

        public double Volume { get; set; }

        Uri source;
        public Uri Source
        {
            get { return source; }
            set
            {
                source = value;
                if (autoPlay) Play();
            }
        }

        System.IO.Stream streamSource;
        public System.IO.Stream StreamSource
        {
            get { return streamSource; }
            set
            {
                streamSource = value;
                if (autoPlay) Play();
            }
        }

        public Size NaturalVideoSize
        {
            get
            {
                if (captureSource == null || captureSource.VideoCaptureDevice.DesiredFormat == null)
                    return Size.Empty;
                else
                    return new Size(captureSource.VideoCaptureDevice.DesiredFormat.PixelWidth, captureSource.VideoCaptureDevice.DesiredFormat.PixelHeight);
            }
        }

        Stretch stretch = default(Stretch);
        public Stretch Stretch
        {
            get
            {
                return stretch;
            }
            set
            {
                stretch = value;
                if (videoBrush != null)
                    videoBrush.Stretch = stretch;
            }
        }

        public bool EnableGPUAcceleration
        {
            get
            {
                return !(rectVideo.CacheMode == null);
            }
            set
            {
                if (value)
                    rectVideo.CacheMode = new BitmapCache();
                else
                    rectVideo.CacheMode = null;
            }
        }

        public FrameworkElement VisualElement
        {
            get { return rectVideo; }
        }

        public double DroppedFramesPerSecond
        {
            get { return 0; }
        }

        public double RenderedFramesPerSecond
        {
            get { return captureSource.VideoCaptureDevice.DesiredFormat.FramesPerSecond; }
        }

        public bool SupportsAdScheduling
        {
            get { return false; }
        }

        public event Action<IMediaPlugin, double> BufferingProgressChanged;

        public event Action<IMediaPlugin, double> DownloadProgressChanged;

        public event Action<IMediaPlugin, MediaMarker> MarkerReached;

        public event Action<IMediaPlugin> MediaEnded;

        public event Action<IMediaPlugin, Exception> MediaFailed;

        public event Action<IMediaPlugin> MediaOpened;

        public event Action<IMediaPlugin> PlaybackRateChanged;

        public event Action<IMediaPlugin, MediaPluginState> CurrentStateChanged;

        public event Action<IMediaPlugin> SeekCompleted;

        public event Action<IAdaptiveMediaPlugin, IAdContext> AdClickThrough;

        public event Action<IAdaptiveMediaPlugin, IAdContext> AdError;

        public event Action<IAdaptiveMediaPlugin, IAdContext, AdProgress> AdProgressUpdated;

        public void Play()
        {
            if (captureSource == null)
            {
                // no capture source is set but we were told to play so just load the first one
                CaptureSource = new CaptureSource()
                {
                    VideoCaptureDevice = CaptureDeviceConfiguration.GetAvailableVideoCaptureDevices().First()
                };
            }

            if (captureSource.State != CaptureState.Started)
            {
                if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
                {
                    captureSource.Start();
                    rectVideo.Fill = videoBrush;
                    if (MediaOpened != null) MediaOpened(this);
                    ResetState();
                }
                else if (captureSource.State == CaptureState.Failed)
                {
                    if (MediaFailed != null) MediaFailed(this, new Exception("Webcam could not be started"));
                    ResetState();
                }
            }
        }

        public void Pause()
        {
            captureSource.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(captureSource_CaptureImageCompleted);
            captureSource.CaptureImageAsync();
        }

        void captureSource_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
        {
            captureSource.CaptureImageCompleted -= new EventHandler<CaptureImageCompletedEventArgs>(captureSource_CaptureImageCompleted);
            if (e.Error == null)
            {
                rectVideo.Fill = new ImageBrush() { ImageSource = e.Result, Stretch = this.Stretch };
                captureSource.Stop();
                CurrentState = MediaPluginState.Paused;
            }
        }

        public void Stop()
        {
            captureSource.Stop();
            if (MediaEnded != null) MediaEnded(this);
            ResetState();
        }

        public void RequestLog()
        {
            // do nothing
        }

        public IAdContext ScheduleAd(Uri adSource, DeliveryMethods deliveryMethod, TimeSpan? duration = null, TimeSpan? startTime = null, TimeSpan? startOffset = null, Uri clickThrough = null, bool pauseTimeline = true, IAdContext appendToAd = null, object data = null)
        {
            throw new NotImplementedException();
        }

        bool isLoaded;
        public bool IsLoaded
        {
            get { return isLoaded; }
        }

        public event Action<IPlugin, LogEntry> LogReady;

        public event Action<IPlugin> PluginLoaded;

        public event Action<IPlugin> PluginUnloaded;

        public event Action<IPlugin, Exception> PluginLoadFailed;

        public event Action<IPlugin, Exception> PluginUnloadFailed;

        public void Load()
        {
            try
            {
                videoBrush = new VideoBrush() { Stretch = stretch };
                rectVideo = new Rectangle();

                if (captureSource != null)
                {
                    videoBrush.SetSource(captureSource);
                    rectVideo.Fill = videoBrush;
                    CurrentState = MediaPluginState.Opening;
                }
                else
                {
                    CurrentState = MediaPluginState.Stopped;
                }

                isLoaded = true;
                if (PluginLoaded != null) PluginLoaded(this);

            }
            catch (Exception ex)
            {
                if (PluginLoadFailed != null) PluginLoadFailed(this, ex);
            }
        }

        public void Unload()
        {
            try
            {
                if (captureSource != null)
                {
                    captureSource.Stop();
                }

                videoBrush = null;
                rectVideo = null;

                CurrentState = MediaPluginState.Closed;

                isLoaded = false;
                if (PluginUnloaded != null) PluginUnloaded(this);
            }
            catch (Exception ex)
            {
                if (PluginUnloadFailed != null) PluginUnloadFailed(this, ex);
            }
        }

        CaptureSource captureSource;
        public CaptureSource CaptureSource
        {
            get { return captureSource; }
            set
            {
                captureSource = value;
                if (captureSource.VideoCaptureDevice.DesiredFormat == null)
                    captureSource.VideoCaptureDevice.DesiredFormat = GetBestFormat(captureSource.VideoCaptureDevice.SupportedFormats);

                if (isLoaded)
                {
                    videoBrush.SetSource(captureSource);
                    rectVideo.Fill = videoBrush;
                }
            }
        }

        public static VideoFormat GetBestFormat(IEnumerable<VideoFormat> supportedFormats)
        {
            var groupsByResolution = supportedFormats.GroupBy(f => f.PixelHeight * f.PixelWidth);
            var bestResolutionGroup = groupsByResolution.OrderByDescending(gf => gf.Key).First();
            var groupsByFramerate = bestResolutionGroup.GroupBy(f => f.FramesPerSecond);
            var bestGroup = groupsByFramerate.OrderByDescending(gf => gf.Key).First();
            return bestGroup.First();
        }
    }
}
